<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <script>
      let W = null;

      function Vue({ data }) {
        this._data = data;

        //    数据代理
        for (let key in this._data) {
          Object.defineProperty(this, key, {
            get: function ProxyGetter() {
              return this._data[key];
            },
            set: function ProxySetter(val) {
              this._data[key] = val;
            },
          });
        }
        observe(this._data);
      }

      //首先封装一个函数,用来递归检测当前被劫持的值是否是一个对象(判断这个值是否可以作为一个发布者)
      function observe(value) {
        if (typeof value !== "object" || value === null) {
          return;
        }

        //一旦判断通过,当前的数据需要被数据劫持,我们就实例化一个Observer(发布者)
        new Observer(value);
      }

      //封装一个发布者的类(数据劫持)

      class Observer {
        constructor(value) {
          this.value = value;

          //判断当前的值是否是一个数组,数组是不需要做数据劫持的,但是数组的内部如果是对象,则这个对象是要重新进行数据劫持的
          if (Array.isArray(this.value)) {
            this.observeArray();
          } else {
            this.walk();
          }
        }

        observeArray() {
          this.value.forEach((item) => {
            observe(item);
          });
        }

        // 当发布者接受的是一个对象的时候,进行数据劫持
        walk() {
          for (let key in this.value) {
            let value = this.value[key];
            defineReactive(this.value, key, value);
          }
        }
      }

      function defineReactive(obj, key, value) {
        //先拿value的值进行去observe函数中进行判断,是否需要再次劫持
        observe(value);

        //一旦一个对象能够进来defineReactive函数进行劫持,说明这个对象就是一个发布者
        //每一个发布者其实是对应一个订阅中心的,所以在defineReactive中要实例化一个订阅中心
        let dep = new Dep();

        Object.defineProperty(obj, key, {
          get: function ReactiveGetter() {
            console.log("数据被获取了");

            if (W) {
              dep.depend(W);
            }
            return value;
          },
          set: function ReactiveSetter(newVal) {
            // 判断新设置的值是否和之前的value一致，如果一致就返回
            if (newVal === value) {
              return;
            }
            console.log("数据被设置了");
            value = newVal;

            //一旦劫持到更新数据,我们要调用中心的notify方法,通知订阅者重新获取数据
            dep.notify();

            // 新值可能是个对象，对新值也要进行判断是否劫持
            observe(newVal);
          },
        });
      }

      // 实例化一个订阅中心的类
      class Dep {
        constructor() {
          this.subs = [];
        }

        depend(watcher) {
          this.subs.push(watcher);
        }

        notify() {
          this.subs.forEach((item) => {
            item.update();
          });
        }
      }

      // 封装一个观察者的类
      class Watcher {
        constructor(data, key) {
          this.data = data;
          this.key = key;

          W = this;
          this.get();
          W = null;
        }

        get() {
          console.log("watcher读取了数据");
          return this.data[this.key];
        }
        update() {
          console.log("watcher更新了视图，最新的值是" + this.get());
        }
      }

      let vm = new Vue({
        data: {
          name: "pp",
          age: 22,
          friend: ["nancy", "judy", "lily"],
          score: {
            ch: 120,
            en: 110,
          },
        },
      });

      new Watcher(vm.score, "ch");
    </script>
  </body>
</html>
